/*
 * pcfgTreeReader.java
 *
 * Created on July 12, 2006, 11:40 AM
 *
 */

package galronnlp.io;

import galronnlp.util.*;
import java.io.IOException;
import java.io.Reader;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.regex.*;
/**
 *
 * @author Daniel A. Galron
 */
public class pcfgTreeReader {
    
    private BufferedReader reader;
    private SymbolGenerator gen = new SymbolGenerator();
    private boolean next = true;
    
    /**
     * Creates a new instance of pcfgTreeReader
     */
    public pcfgTreeReader(BufferedReader r, SymbolGenerator g) {
        reader = r;
        gen = g;
    }
    
    public pcfgTreeReader(BufferedReader r) {
        reader = r;
    }
    
    public boolean hasNext() {
        return next;
    }
    
    public void close() {
        try {
            reader.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    /**
     * Read in the next tree from the file. These trees must be in the pcfg Treebank
     * mrg format.
     */
    public Tree readTree() {
        // Get the tree from teh treebank represented as a string
        String treeString = this.readNextTreeString();
        Tree ret = null;
        // If the string is not empty (that is, if the string is not an empty line
        // read from the file, construct the tree and return it.
        if(!treeString.equals("")) {
            try {
                ret = this.constructTree(treeString);
            } catch (malformedTreeException ex) {
                ex.printStackTrace();
            }
        }
        return ret;
    }
    
    private Tree constructTree(String treeString) throws malformedTreeException {
        // Trim whitespace from the tree String
        treeString = treeString.trim();
        // Create a new tree scanner to build this part of the tree
        pcfgTreeScanner ts = new pcfgTreeScanner(treeString, gen);
        // If the next part of the tree is a leaf (that is, it is of format:
        // (X y), then:
        if(ts.hasNextLeaf()) {
            // Trim the parentheses from this leaf
            String noParenTreeString = this.trimParens(ts.nextLeaf());
            // Create a tree scanner especially for the String "X y"
            // INVARIANT: noParenTreeString is of format "X y"
            pcfgTreeScanner ls = new pcfgTreeScanner(noParenTreeString, gen);
            // get the category and lexical item
            if(!ls.hasNextSymbol()) throw new malformedTreeException(noParenTreeString);
            Symbol LHS = ls.nextSymbol();
            if(!ls.hasNextProbability()) throw new malformedTreeException(noParenTreeString);
            double prob = ls.nextProbability();
            if(!ls.hasNextSymbol()) throw new malformedTreeException(noParenTreeString);
            Symbol RHS = ls.nextSymbol();
            // Return a new LexicalTree item containing the LHS and RHS
            return new LexicalTree(LHS, RHS, prob);
        // Otherwise, if the next part of tree is of type constituent, that is,
        // if it is a full subtree, containing one or more other subtrees or lexical items
        // (this corresponds to a production in the recursively built tree.
        } else if(ts.hasNextConstituent()) {
            // Trim the parentheses from this subtree string
            String noParenTreeString = this.trimParens(ts.nextConstituent());
            // Create a tree scanner for this subtree
            pcfgTreeScanner cs = new pcfgTreeScanner(noParenTreeString, gen);
            // INVARIANT: noParenTreeString = LHS (.*)
            if(!cs.hasNextSymbol()) throw new malformedTreeException(cs.wholeString);
            Symbol LHS = cs.nextSymbol();
            if(!cs.hasNextProbability()) throw new malformedTreeException(cs.wholeString);
            double prob = cs.nextProbability();
            LinkedList<Tree> children = new LinkedList<Tree>();
            // for each subsequent subtree which is a child of this level in the tree
            while(cs.hasNextConstituent()) {
                String next = cs.nextConstituent();
                // Recursively construct the tree representation for that subtree, 
                // and add it to the list of children of this side.
                children.addLast(this.constructTree(next.trim()));
            }
            if(children.size() == 1)
                return new UnaryTree(LHS, children.get(0), prob);
            else if(children.size() == 2)
                return new BinaryTree(LHS, children.get(0), children.get(1), prob);
            else
                return new NaryTree(LHS, children, prob);
        // Deals with the case where the next thing read is a pair of open parentheses,
        // signifying (in the case of the WSJ corpus) that the treeString is the top
        // level of the tree.
        } else if(ts.hasNextStart()) {
            String noParenTreeString = this.trimParens(ts.nextStart());
            return this.constructTree(noParenTreeString.trim());
        } else {
            throw new malformedTreeException(treeString);
        }
    }
    
    private String trimParens(String string) {
        int first = string.indexOf('(') + 1;
        int last = string.lastIndexOf(')');
        return string.substring(first, last).trim();
    }
    
    public static void main(String[] args) {
        String tree = "( (S:0.0243\n" +
                      "   (NP-SBJ:0.9\n" +
                      "     (NP:0.54 (NNP:1.34e-5 Pierre) (NNP:0.999 Vinken) )\n" +
                      "     (,:1.0 ,)\n" +
                      "     (ADJP:0.15\n" +
                      "       (NP:0.3 (CD:0.3 61) (NNS:1.0 YEARS) )\n" +
                      "       (JJ:0.5 old) )\n" +
                      "     (,:1.0 ,) )\n" +
                      "   (VP:0.324 (MD:0.3 will)\n" +
                      "     (VP:0.432 (VB:0.6 join)\n" +
                      "       (NP:0.89 (DT:0.5 the) (NN:0.32 board) )\n" +
                      "       (PP-CLR:0.32 (IN:0.43 as)\n" +
                      "         (NP (DT:1.3214e-34 a) (JJ:0.5 nonexecutive) (NN:0.43 director) ))\n" +
                      "       (NP-TMP:0.32 (NNP:0.54 Nov.) (CD:0.7 29) )))\n" +
                      "     (.:1.0 .) ))";
        String tree2 = "(NP:0.43 dog)";
        pcfgTreeReader r = new pcfgTreeReader(new BufferedReader(new StringReader(tree)));
        //System.out.println(r.readNextTreeString());
        System.out.println(r.readTree());
    }

    private String readNextTreeString() {
        String tree = "";
        int parensSeen = 0;
        try {
            // Read the first line in the reader, and count the number of open parentheses.
            String line = reader.readLine();
            // Skip over blank lines
            while(line.length() == 0) {
                line = reader.readLine();
            }
            for(int i = 0; i < line.length(); i++) {
                if(line.charAt(i) == '(') parensSeen++;
                else if(line.charAt(i) == ')') parensSeen--;
            }
            tree = line.trim();
        } catch (IOException ex) {
            next = false;
            return "";
        } catch(NullPointerException ex) {
            // Exception is thrown when the reader has reached the end of file.
            // This means that there aren't any trees left in the file, which causes
            // hasNext() to be false
            next = false;
            return "";
        }        
        //System.out.println(parensSeen);
        // Read in the next lines until close parentheses have been found for all open
        // parentheses
        while(parensSeen != 0) {
            try {
                String line = reader.readLine();
                for(int i = 0; i < line.length(); i++) {
                    if(line.charAt(i) == '(') parensSeen++;
                    else if(line.charAt(i) == ')') parensSeen--;
                }
                tree += line;
            } catch(IOException ex) {
                next = false;
                return "";
            } catch(NullPointerException ex) {
                next = false;
                return "";
            }
        }
        return tree + "\n";
    }
    
}
